package masses;

import forces.Force;
import forces.Gravity;
import forces.Viscosity;
import simulations.Entity;
import simulations.Simulation;
import java.awt.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;


/**
 * @author Robert C. Duvall
 *         edited by Jenni Mercado
 */
public class Mass {
    /**
     * a constant size used by all Masses
     */
    private static final Dimension SIZE = new Dimension(16, 16);

    /**
     * constants used for Force directions
     */
    private static final int UP = 90;
    private static final int RIGHT = 0;
    private static final int LEFT = 180;
    private static final int DOWN = 270;

    // state
    private Point2D myCenter;
    private Force myVelocity;
    private Dimension mySize;
    private int myID;
    private double myMass;
    private Force myAcceleration;
    private double myBounceMagnitude = 2;

    /**
     * @param id
     * @param x
     * @param y
     * @param mass
     */
    public Mass (int id, double x, double y, double mass) {
        myAcceleration = new Force();
        myMass = mass;
        myID = id;
        setCenter(x, y);
        setVelocity(0, 0);
        mySize = SIZE;
        
    }

    public void paint (Graphics2D pen) {
        pen.setColor(Color.BLACK);
        pen.fillOval(getLeft(), getTop(), getSize().width, getSize().height);
    }

    public void update (Simulation canvas, double dt) {
        // apply optional forces of gravity, viscosity, wallreplusion
        applyOptionalForces(canvas);
        applyForce(getBounce(canvas.getSize()));
        // convert force back into Mover's velocity
        getVelocity().sum(myAcceleration);
        myAcceleration.reset();
        // move mass by velocity
        myCenter.setLocation(myCenter.getX() + myVelocity.getXChange() * dt,
                myCenter.getY() + myVelocity.getYChange() * dt);
    }

    /**
     * @param f force to be applied to this mass
     */
    public void applyForce (Force f) {
        myAcceleration.sum(f);
    }

    /**
     * determines if a Mass matches the given id
     *
     * @param id
     * @return true if matched
     */
    public boolean match (int id) {
        return myID == id;
    }

    public void applyOptionalForces (Simulation sim) {
        List<Force> globalForces = new ArrayList<Force>();
        globalForces.add(sim.getGravity());
        globalForces.add(sim.getViscosity());
        globalForces.addAll(sim.getWallRepulsions());
        if (!globalForces.isEmpty()) {
            for (Force f : globalForces) {
                f.update(this);
                this.applyForce(f);
            }
        }
    }

    // check for move out of bounds
    private Force getBounce (Dimension bounds) {
        Force impulse = new Force();
        if (getLeft() < 0) {
            impulse = new Force(RIGHT, myBounceMagnitude);
            setCenter(getSize().width / 2, getCenter().getY());
        }
        else if (getRight() > bounds.width) {
            impulse = new Force(LEFT, myBounceMagnitude);
            setCenter(bounds.width - getSize().width / 2, getCenter().getY());
        }
        if (getTop() < 0) {
            impulse = new Force(DOWN, myBounceMagnitude);
            setCenter(getCenter().getX(), getSize().height / 2);
        }
        else if (getBottom() > bounds.height) {
            impulse = new Force(UP, myBounceMagnitude);
            setCenter(getCenter().getX(), bounds.height - getSize().height / 2);
        }
        impulse.scale(getVelocity().getRelativeMagnitude(impulse));
        return impulse;
    }

    /**
     * Returns shape's velocity.
     */
    public Force getVelocity () {
        return myVelocity;
    }

    /**
     * Resets shape's velocity.
     */
    public void setVelocity (double direction, double magnitude) {
        myVelocity = new Force(direction, magnitude);
    }

    /**
     * Returns shape's center.
     */
    public Point2D getCenter () {
        return myCenter;
    }

    /**
     * Resets shape's center.
     */
    public void setCenter (double x, double y) {
        myCenter = new Point2D.Double(x, y);
    }

    /**
     * Returns shape's left-most coordinate.
     */
    public int getLeft () {
        return (int) (getCenter().getX() - getSize().width / 2);
    }

    /**
     * Returns shape's top-most coordinate.
     */
    public int getTop () {
        return (int) (getCenter().getY() - getSize().height / 2);
    }

    /**
     * Returns shape's right-most coordinate.
     */
    public int getRight () {
        return (int) (getCenter().getX() + getSize().width / 2);
    }

    /**
     * Reports shape's bottom-most coordinate.
     * 
     * @return bottom-most coordinate
     */
    public int getBottom () {
        return (int) (getCenter().getY() + getSize().height / 2);
    }

    /**
     * Returns shape's size.
     */
    public Dimension getSize () {
        return mySize;
    }

    public double getMyMass() {
        return myMass;
    }
}
